/*
 * UserNet  --  TCP/IP implementation in userspace
 * Copyright (C) 2010  Robert Millan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <error.h>
#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>		/* strncpy */
#include <sys/ioctl.h>
#include <net/if.h>

#include <pthread.h>
#include <pcap.h>

#include "unet/misc.h"
#include "unet/dev.h"
#include "unet/pcap.h"
#include "unet/ether.h"

struct unet_pcap_driver
{
  int pcap_datalink;
  pcap_t *pcap_handle;
  pthread_mutex_t pcap_send_mutex;
  pthread_mutex_t pcap_recv_mutex;
};

static void *
loop (void *arg)
{
  struct unet_device *device = arg;
  struct unet_pcap_driver *pcap_driver = device->driver.this;

  while (1)
    {
      const uint8_t *packet;
      struct pcap_pkthdr packet_info;

      pthread_mutex_lock (& pcap_driver->pcap_recv_mutex);
      packet = pcap_next (pcap_driver->pcap_handle, &packet_info);
      pthread_mutex_unlock (& pcap_driver->pcap_recv_mutex);

      if (packet == NULL)
	error (1, 0, "unet%u stopped working", device->id);

      switch (pcap_driver->pcap_datalink)
	{
	case DLT_EN10MB:
	  unet_ethernet_incoming_packet (device, packet, packet_info.len);
	  break;
	case DLT_IEEE802_11:
	  // FIXME
	default:
	  /* Should never happen.  */
	  break;
	}
    }

  return NULL;
}

static void
unet_pcap_send_packet (struct unet_device *device, const uint8_t *packet, const uint32_t len)
{
  struct unet_pcap_driver *pcap_driver = device->driver.this;

  pthread_mutex_lock (& pcap_driver->pcap_send_mutex);
  (void) pcap_sendpacket (pcap_driver->pcap_handle, packet, len);
  pthread_mutex_unlock (& pcap_driver->pcap_send_mutex);
}

int
unet_pcap_init (const char *device_name)
{
  char errbuf[PCAP_ERRBUF_SIZE];
  struct unet_device *device;
  pthread_t pcap_thread;
  int sock = socket (AF_INET, SOCK_DGRAM, 0);
  struct ifreq dev;

  device = unet_zalloc (sizeof (*device));
  if (! device)
    return -1;
  device->port_list = unet_zalloc (2 * 65536 * sizeof (void *));
  if (! device->port_list)
    return -1;
  struct unet_pcap_driver *pcap_driver = unet_zalloc (sizeof (*pcap_driver));
  if (! pcap_driver)
    return -1;
  pthread_mutex_init (& pcap_driver->pcap_send_mutex, NULL);
  pthread_mutex_init (& pcap_driver->pcap_recv_mutex, NULL);
  device->driver.this = pcap_driver;

  strncpy (device->driver.name, "PCAP", sizeof (device->driver.name));

  strncpy (dev.ifr_name, device_name, sizeof (dev.ifr_name));

  /* Obtain MAC address.  */
  {
    if (ioctl (sock, SIOCGIFHWADDR, &dev) < 0)
      error (1, errno, "SIOCGIFHWADDR ioctl");

    int i;
    for (i = 0; i < ETH_ALEN; i++)
      device->mac_addr[i] = dev.ifr_hwaddr.sa_data[i] & 0xff;
  }

  /* Activate device, just in case it isn't already.  */
  ioctl (sock, SIOCGIFFLAGS, &dev);
  dev.ifr_flags |= IFF_UP;
  ioctl (sock, SIOCSIFFLAGS, &dev);

  if (ioctl (sock, SIOCGIFADDR, &dev) != -1)
    error (1, 0, "%s is being used by kernel. Hint: ifconfig %s 0.0.0.0", device_name, device_name);

  pcap_driver->pcap_handle = pcap_open_live (device_name,
					     65535 /* max packet size */,
					     0 /* disable promiscous mode */,
					     0 /* no timeout */,
					     errbuf);
  if (pcap_driver->pcap_handle == NULL)
    error (1, 0, "pcap_open_live: %s\n", errbuf);

  device->driver.send_packet = unet_pcap_send_packet;

  /* Map supported datalink types only.  */
  pcap_driver->pcap_datalink = pcap_datalink (pcap_driver->pcap_handle);
  switch (pcap_driver->pcap_datalink)
    {
    case DLT_EN10MB:
      device->driver.datalink = unet_ether_datalink;
      break;
    case DLT_IEEE802_11:
      // FIXME
    default:
      error (1, 0, "unsupported data link type\n");
      break;
    }

  unet_device_add (device);

  pthread_create (&pcap_thread,
		  NULL /* default attributes */,
		  loop, device);

  return 0;
}
